package com.bot.devbot.service.deploy;

import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 框架发布服务
 *
 * @author xialiguo
 * @version 1.0
 * @date 2025/2/17 9:38
 * @since 1.0
 */
@Slf4j
public class FrameWorkDeployUtil {
    // 项目发布工作空间,与开发空间区分开,这里只用于框架发布
    public final static String deployProjectFilePath = "D:\\auto-deploy-workspaces";

    public static void deployFilter(String projectName, String branchName) {
        if ("framework".equals(projectName)) {
            log.info("deploy {} {}分支", projectName, branchName);
            // 使用线程池异步执行
            new Thread(() -> deploy(projectName, branchName)).start();
        }
        if ("charge-platform".equals(projectName)) {
            log.info("deploy {} {}分支", projectName, branchName);
            new Thread(() -> deploy(projectName, branchName)).start();
        }
    }

    /**
     * Windows 下切换至 deployProjectFilePath 目录下，切换 deployBranch 分支，
     * 然后调用 git 执行 git pull 拉取最新分支（已安装 git），然后仅对有代码变更的模块执行 deploy，发布项目
     *
     * @param projectName 项目名称
     * @param branchName  分支名称
     */
    public static void deploy(String projectName, String branchName) {
        try {
            // 构建完整的项目路径
            String projectPath = deployProjectFilePath + "\\" + projectName;

            // 验证项目路径是否存在
            File projectDir = new File(projectPath);
            if (!projectDir.exists() || !projectDir.isDirectory()) {
                throw new IOException("项目路径不存在或无效: " + projectPath);
            }

            // 构建 ProcessBuilder
            ProcessBuilder processBuilder = new ProcessBuilder();
            processBuilder.directory(projectDir); // 设置工作目录
            processBuilder.redirectErrorStream(true); // 将错误流和标准输出流合并

            executeCommandAndGetOutput(processBuilder, "chcp 65001");
            // 1. 切换分支
            log.info("正在切换分支到 {}...", branchName);
            executeCommandAndGetOutput(processBuilder, "git checkout " + branchName);

            // 2. 拉取最新代码
            log.info("正在拉取最新代码...");
            executeCommandAndGetOutput(processBuilder, "git pull");

            // 3. 获取有代码变更的模块
            List<String> changedFiles = getChangedFiles(processBuilder);
            List<String> changedModules = getChangedModules(changedFiles);

            // 如果是是charge-platform项目，changedModules只保留hlht-core、charge-platform-client
            if ("charge-platform".equals(projectName)) {
                changedModules = changedModules.stream()
                        .filter(module -> module.startsWith("hlht-core") || module.startsWith("hlht-platform-client"))
                        .collect(Collectors.toList());
            }

            if (changedModules.isEmpty()) {
                log.info("没有检测到任何代码变更，无需部署！");
                return;
            }

            // 4. 检查是否需要执行父项目级别的部署
            if (isParentPomChanged(changedFiles)) {
                log.info("父项目的 pom.xml 发生变更，执行父项目级别的部署...");
                executeCommandAndGetOutput(processBuilder, "mvn clean deploy -Dmaven.test.skip=true -N");
            }

            // 5. 对有代码变更的模块执行部署
            log.info("以下模块有代码变更，将进行部署：{}", String.join(", ", changedModules));
            for (String module : changedModules) {
                deployModule(module, projectPath);
            }

            log.info("项目 {} 发布成功！", projectName);

        } catch (Exception e) {
            log.error("项目发布失败：", e);
        }
    }

    /**
     * 检查父项目的 pom.xml 是否发生变更
     *
     * @param changedFiles 变更的文件列表
     * @return 如果父项目的 pom.xml 发生变更，返回 true；否则返回 false
     */
    private static boolean isParentPomChanged(List<String> changedFiles) {
        return changedFiles.contains("pom.xml");
    }

    /**
     * 获取有代码变更的文件列表
     *
     * @param processBuilder ProcessBuilder 对象
     * @return 变更的文件列表
     */
    private static List<String> getChangedFiles(ProcessBuilder processBuilder) throws IOException, InterruptedException {
        return executeCommandAndGetOutput(processBuilder, "git diff --name-only HEAD~1");
    }

    /**
     * 获取有代码变更的模块
     *
     * @param changedFiles 变更的文件列表
     * @return 受影响的模块列表
     */
    private static List<String> getChangedModules(List<String> changedFiles) {

        if (changedFiles.isEmpty()) {
            log.warn("未检测到任何代码变更！");
            return new ArrayList<>();
        }

        // 解析受影响的模块
        return changedFiles.stream()
                .filter(file -> !file.isEmpty()) // 过滤掉空行
                .map(file -> normalizePath(file).split(Pattern.quote(File.separator))[0]) // 提取文件所属的模块名
                .distinct() // 去重
                // 去除可能带.的文件
                .filter(module -> !module.contains("."))
                // 去除document、sql目录
                .filter(module -> !module.contains("document"))
                .filter(module -> !module.contains("sql"))
                .collect(Collectors.toList());
    }

    /**
     * 统一路径分隔符，将所有 / 替换为 File.separator
     *
     * @param path 原始路径
     * @return 统一后的路径
     */
    private static String normalizePath(String path) {
        return path.replace("/", File.separator);
    }

    /**
     * 部署指定模块
     *
     * @param module      模块名称
     * @param projectPath 项目路径
     * @throws IOException 如果发生 I/O 错误
     */
    private static void deployModule(String module, String projectPath) throws IOException, InterruptedException {
        // 构建模块路径
        String modulePath = projectPath + "\\" + module;

        // 构建 ProcessBuilder
        ProcessBuilder processBuilder = new ProcessBuilder();
        processBuilder.directory(new File(modulePath)); // 设置工作目录为模块路径
        processBuilder.redirectErrorStream(true); // 将错误流和标准输出流合并

        // 执行部署命令
        log.info("正在部署模块：{}", module);
        // -T 4C 并行构建
        executeCommandAndGetOutput(processBuilder, "mvn clean deploy -T 4C");
    }

    /**
     * 执行命令并返回输出结果
     *
     * @param processBuilder ProcessBuilder 对象
     * @param command        命令字符串
     * @return 命令输出结果
     * @throws IOException 如果发生 I/O 错误
     */
    private static List<String> executeCommandAndGetOutput(ProcessBuilder processBuilder, String command) throws IOException, InterruptedException {
        File workingDir = processBuilder.directory(); // 获取工作目录
        log.info("工作目录: {}, 正在执行命令: {}", workingDir != null ? workingDir.getAbsolutePath() : "默认", command);
        processBuilder.command("cmd.exe", "/c", command);
        Process process = processBuilder.start();

        List<String> output = new ArrayList<>();
        try (
                BufferedReader stdReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                BufferedReader errReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))
        ) {
            String line;
            while ((line = stdReader.readLine()) != null) {
                log.info("标准输出: {}", line); // 记录标准输出
                output.add(line.trim());
            }
            while ((line = errReader.readLine()) != null) {
                log.error("错误输出: {}", line); // 记录错误输出
            }
        }

        int exitCode = process.waitFor();
        if (exitCode != 0) {
            throw new IOException("命令执行失败，退出码：" + exitCode);
        }

        return output;
    }
}
